/*
 * Copyright (C) 2021 XRADIO TECHNOLOGY CO., LTD. All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *    1. Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *    2. Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the
 *       distribution.
 *    3. Neither the name of XRADIO TECHNOLOGY CO., LTD. nor the names of
 *       its contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 *  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 *  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 *  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdio.h>
#include <string.h>
#include "os.h"
#include "common/cmd/cmd.h"
#include "wifi_device.h"
#include "cmd_hm_net_ap.h"

#define cmd_nitems(a) (sizeof((a)) / sizeof((a)[0]))
#define WIFI_DEVICE_SCAN_RESULT_MAX (30)

static WifiEvent sta_event;
WifiScanInfo scan_results[WIFI_DEVICE_SCAN_RESULT_MAX];
static unsigned int scan_num = 0;
static int netId = 0;
static char ScanState = 0;

static enum cmd_status Printf_Scan_Result(unsigned int *num) 
{
	if (WIFI_SUCCESS != GetScanInfoList(scan_results, num)) {
		printf("Error : wifi get scan_result fail.\n");
		return CMD_STATUS_FAIL;
	}

	printf("Scan success, results :\n");
	for (int i = 0; i < *num; i++) {
		printf("scan result num %d: ", i + 1);
		printf("ssid: %s ", scan_results[i].ssid);
		printf("bssid: ");
		printf("%02X%02X%02X%02X%02X%02X ", scan_results[i].bssid[0],
		       scan_results[i].bssid[1], scan_results[i].bssid[2],
		       scan_results[i].bssid[3], scan_results[i].bssid[4],
		       scan_results[i].bssid[5]);
		printf("securityType: %d ", scan_results[i].securityType);
		printf("rssi: %d ", scan_results[i].rssi);
		printf("band: %dMHZ ", scan_results[i].band);
		printf("frequency: %dMHZ\n", scan_results[i].frequency);
	}	
	return CMD_STATUS_OK;
}

static void Scan_done_deal(int state, int size)
{
	if (state == WIFI_STATE_AVALIABLE) {
		printf("======== Callback: scan done, the num of bss: %d\n",
		       size);
		if (ScanState == 1) {
			ScanState = 0;
			scan_num = 30;
			Printf_Scan_Result(&scan_num);
		}
	}
}

static void Connected_deal(int state, WifiLinkedInfo *info)
{
	if (state == WIFI_STATE_AVALIABLE) {
		printf("======== Callback: connected\n");

	} else if (state == WIFI_STATE_NOT_AVALIABLE) {
		printf("======== Callback: disconnected\n");
	}
}

WifiEvent *STA_CallBack_Register(void)
{
	return &sta_event;
}

static enum cmd_status cmd_net_sta_enable_exec(char *cmd)
{
	if (IsHotspotActive() == WIFI_HOTSPOT_ACTIVE) {
		if (DisableHotspot() != WIFI_SUCCESS) {
			printf("disable ap fail!\n");
			return ERROR_WIFI_BUSY;
		}
		UnRegisterWifiEvent(AP_CallBack_Register());
	}

	if (WIFI_SUCCESS != EnableWifi()) {
		printf("Error : wifi enable fail.\n");
		return CMD_STATUS_FAIL;
	}

	sta_event.OnWifiScanStateChanged = Scan_done_deal;
	sta_event.OnWifiConnectionChanged = Connected_deal;

	if (WIFI_SUCCESS != RegisterWifiEvent(&sta_event)) {
		printf("Error: RegisterWifiEvent fail\n");
		return CMD_STATUS_FAIL;
	}

	return CMD_STATUS_OK;
}

static enum cmd_status cmd_net_sta_disable_exec(char *cmd)
{
	if (WIFI_SUCCESS != DisableWifi()) {
		printf("Error: DisableWifi fail.\n");
		return CMD_STATUS_FAIL;
	}

	if (WIFI_SUCCESS != UnRegisterWifiEvent(&sta_event)) {
		printf("Error: UnRegisterWifiEvent fail\n");
		return CMD_STATUS_FAIL;
	}

	return CMD_STATUS_OK;
}

static enum cmd_status cmd_net_scan_once_exec(char *cmd)
{
	scan_num = 0;
	memset(&scan_results, 0,
	       WIFI_DEVICE_SCAN_RESULT_MAX * (sizeof(WifiScanInfo)));

	if (WIFI_SUCCESS != Scan()) {
		printf("Error : wifi scan fail.\n");
		return CMD_STATUS_FAIL;
	}
	ScanState = 1;
	return CMD_STATUS_OK;
}

static enum cmd_status cmd_net_scan_result_exec(char *cmd)
{
	int32_t cnt;

	cnt = cmd_sscanf(cmd, "n=%d", &scan_num);
	if ((cnt != 1) || (scan_num == 0)) {
		return CMD_STATUS_INVALID_ARG;
	}
	if (scan_num > WIFI_DEVICE_SCAN_RESULT_MAX) {
		scan_num = WIFI_DEVICE_SCAN_RESULT_MAX;
	}

	return Printf_Scan_Result(&scan_num);
}

static enum cmd_status cmd_net_add_exec(char *cmd)
{
	WifiErrorCode error;
	const char psk[WIFI_MAX_KEY_LEN];
	char *argv[3];
	WifiDeviceConfig config = { 0 };

	if (cmd_parse_argv(cmd, argv, cmd_nitems(argv)) == 0) {
		return CMD_STATUS_INVALID_ARG;
	}

	memcpy(config.ssid, (uint8_t *)argv[0], cmd_strlen(argv[0]));
	if (cmd_strlen(argv[1])) {
		memcpy(config.preSharedKey, (uint8_t *)argv[1],
		       cmd_strlen(argv[1]));
		if (0 == strcmp(argv[2], "wep")) {
			config.securityType = WIFI_SEC_TYPE_WEP;
		} else if (0 == strcmp(argv[2], "psk")) {
			config.securityType = WIFI_SEC_TYPE_PSK;
		} else if (0 == strcmp(argv[2], "sae")) {
			config.securityType = WIFI_SEC_TYPE_SAE;
		} else {
		}
		printf("target ssid %s\n", argv[0]);
		printf("target psk %s\n", argv[1]);
	} else {
		config.securityType = WIFI_SEC_TYPE_OPEN;
		printf("target ssid %s\n", argv[0]);
		printf("target psk empty\n");
	}
	config.wapiPskType = WIFI_PSK_TYPE_ASCII;

	if (WIFI_SUCCESS != AddDeviceConfig(&config, &netId)) {
		printf("Error: AddDeviceConfig Fail\n");
		return CMD_STATUS_FAIL;
	}
	printf("AddDeviceConfig Success.\n");
	return CMD_STATUS_OK;
}

static enum cmd_status cmd_net_auto_connect_exec(char *cmd)
{
	WifiErrorCode error;
	int cnt, target;
	cnt = cmd_sscanf(cmd, "n=%d", &target);
	if (cnt != 1) {
		return CMD_STATUS_INVALID_ARG;
	}

	error = ConnectTo(target);
	if (WIFI_SUCCESS != error) {
		printf("Error %d: ConnectTo Fail\n", error);
		return CMD_STATUS_FAIL;
	}
	printf("ConnectTo Success\n");

	return CMD_STATUS_OK;
}

static enum cmd_status cmd_net_connect_exec(char *cmd)
{
	WifiErrorCode error;
	const char ssid_want_connect[WIFI_MAX_SSID_LEN];
	const char psk[WIFI_MAX_KEY_LEN];
	char *argv[2];

	if (cmd_parse_argv(cmd, argv, cmd_nitems(argv)) == 0) {
		return CMD_STATUS_INVALID_ARG;
	}

	memset(ssid_want_connect, 0, WIFI_MAX_SSID_LEN);
	memcpy(ssid_want_connect, (uint8_t *)argv[0], cmd_strlen(argv[0]));
	if (cmd_strlen(argv[1])) {
		memset(psk, 0, WIFI_MAX_KEY_LEN);
		memcpy(psk, (uint8_t *)argv[1], cmd_strlen(argv[1]));

		printf("target ssid %s\n", argv[0]);
		printf("target psk %s\n", argv[1]);
	} else {
		memset(psk, 0, WIFI_MAX_KEY_LEN);
		printf("target ssid %s\n", argv[0]);
		printf("target psk empty\n");
	}

	WifiDeviceConfig config = { 0 };
	int i = 0;

	for (i = 0; i < scan_num; i++) {
		if (0 == strcmp(scan_results[i].ssid, ssid_want_connect)) {
			memcpy(config.ssid, scan_results[i].ssid,
			       WIFI_MAX_SSID_LEN);
			memcpy(config.bssid, scan_results[i].bssid,
			       WIFI_MAC_LEN);
			config.securityType = scan_results[i].securityType;
			config.wapiPskType = WIFI_PSK_TYPE_ASCII;
			strcpy(config.preSharedKey, psk);
			config.freq = scan_results[i].frequency;
			break;
		}
	}

	if ((scan_results == NULL) || (i >= scan_num)) {
		memcpy(config.ssid, ssid_want_connect, WIFI_MAX_SSID_LEN);	
		if (strlen(psk) == 0) {
			config.securityType = WIFI_SEC_TYPE_OPEN;
		} else {
			strcpy(config.preSharedKey, psk);
			config.securityType = WIFI_SEC_TYPE_PSK;
			config.wapiPskType = WIFI_PSK_TYPE_ASCII;
		}
	}
	printf("GetScanInfoList Success.\n");
	if (WIFI_SUCCESS != AddDeviceConfig(&config, &netId)) {
		printf("Error: AddDeviceConfig Fail\n");
		return CMD_STATUS_FAIL;
	}
	printf("AddDeviceConfig Success.\n");
	error = ConnectTo(netId);
	if (WIFI_SUCCESS != error) {
		printf("Error %d: ConnectTo Fail\n", error);
		return CMD_STATUS_FAIL;
	}
	printf("ConnectTo Success\n");

	return CMD_STATUS_OK;
}

static enum cmd_status cmd_net_device_info_exec(char *cmd)
{
	WifiDeviceConfig result[WIFI_MAX_CONFIG_SIZE];
	int size;
	int i;
	int res;
	res = GetDeviceConfigs(&result, &size);
	if (WIFI_SUCCESS != res) {
		printf("Error: GetDeviceConfigs Fail\n", res);
	} else {
		if (size > WIFI_MAX_CONFIG_SIZE) {
			size = WIFI_MAX_CONFIG_SIZE;
		}
		printf("GetDeviceConfigs success, result size = %d\n", size);
		for (i = 0; i < size; i++) {
			printf("ssid : %s \n", result[i].ssid);
			printf("bssid: ");
			printf("%02X%02X%02X%02X%02X%02X \n",
			       result[i].bssid[0], result[i].bssid[1],
			       result[i].bssid[2], result[i].bssid[3],
			       result[i].bssid[4], result[i].bssid[5]);
			printf("psk : %s \n", result[i].preSharedKey);
			printf("securityType : %d \n", result[i].securityType);
			printf("netId : %d \n", result[i].netId);
			printf("freq : %d Mhz\n", result[i].freq);
			printf("wapiPskType : %d \n", result[i].wapiPskType);
			printf("ipType : %d \n", result[i].ipType);
		}
	}
	return CMD_STATUS_OK;
}

static enum cmd_status cmd_net_remove_exec(char *cmd)
{
	WifiErrorCode error;
	int cnt, target;
	cnt = cmd_sscanf(cmd, "n=%d", &target);
	if (cnt != 1) {
		return CMD_STATUS_INVALID_ARG;
	}

	error = RemoveDevice(target);
	if (WIFI_SUCCESS != error) {
		printf("Error %d: remove Fail\n", error);
		return CMD_STATUS_FAIL;
	}
	printf("remove Success\n");

	return CMD_STATUS_OK;
}

static enum cmd_status cmd_net_disconnect_exec(char *cmd)
{
	if (WIFI_SUCCESS != Disconnect()) {
		printf("Error: Disconnect Fail\n");
		return CMD_STATUS_FAIL;
	}

	return CMD_STATUS_OK;
}

static enum cmd_status cmd_net_set_exec(char *cmd)
{
	return CMD_STATUS_OK;
}

static enum cmd_status cmd_net_get_exec(char *cmd)
{
	if (cmd_strcmp(cmd, "state") == 0) {
		if (WIFI_STA_ACTIVE == IsWifiActive()) {
			printf("Wifi is active.\n");
		} else {
			printf("Wifi is passive.\n");
		}
	} else if (cmd_strcmp(cmd, "linkinfo") == 0) {
		WifiLinkedInfo get_linked_res;

		if (WIFI_SUCCESS != GetLinkedInfo(&get_linked_res)) {
			printf("Error: GetLinkedInfo Fail\n");
			return CMD_STATUS_FAIL;
		}
		printf("GetLinkedInfo Success.\n");

		printf("ssid: %s\n", get_linked_res.ssid);
		printf("bssid: ");
		for (int j = 0; j < WIFI_MAC_LEN; j++) {
			printf("%02X", get_linked_res.bssid[j]);
		}
		printf("\n");
		printf("rssi: %d\n", get_linked_res.rssi);
	} else if (cmd_strcmp(cmd, "mac") == 0) {
		unsigned char get_mac_res[WIFI_MAC_LEN];

		if (WIFI_SUCCESS != GetDeviceMacAddress(get_mac_res)) {
			printf("Error: GetDeviceMacAddress Fail\n");
			return CMD_STATUS_FAIL;
		}
		printf("GetDeviceMacAddress Success.\n");
		for (int j = 0; j < WIFI_MAC_LEN - 1; j++) {
			printf("%02X:", get_mac_res[j]);
		}
		printf("%02X\n", get_mac_res[WIFI_MAC_LEN - 1]);
	} else {
	}

	return CMD_STATUS_OK;
}

static const struct cmd_data g_net_scan_cmds[] = {
	{ "enable", cmd_net_sta_enable_exec, CMD_DESC("enable command") },
	{ "disable", cmd_net_sta_disable_exec, CMD_DESC("disable command") },
	{ "scan", cmd_net_scan_once_exec, CMD_DESC("scan once command") },
	{ "scan_result", cmd_net_scan_result_exec,
	  CMD_DESC("scan result command") },
	{ "device_info", cmd_net_device_info_exec,
	  CMD_DESC("scan info command") },
	{ "add", cmd_net_add_exec, CMD_DESC("scan add command") },
	{ "remove", cmd_net_remove_exec, CMD_DESC("scan remove command") },
	{ "connect", cmd_net_connect_exec, CMD_DESC("connect command") },
	{ "auto_connect", cmd_net_auto_connect_exec,
	  CMD_DESC("auto connect command") },
	{ "disconnect", cmd_net_disconnect_exec,
	  CMD_DESC("disconnect command") },
	{ "set", cmd_net_set_exec, CMD_DESC("set command") },
	{ "get", cmd_net_get_exec, CMD_DESC("get command") },
};

enum cmd_status cmd_hm_net_sta_exec(char *cmd)
{
	return cmd_exec(cmd, g_net_scan_cmds, cmd_nitems(g_net_scan_cmds));
}
